<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>工程遥测</title>
  <link rel="stylesheet" href="/static/css/bootstrap.min.css">
  <link rel="stylesheet" href="/static/css/font-awesome.min.css">
  <link rel="stylesheet" href="/static/css/style.css">
  <link rel="stylesheet" href="./../qv.css">
  <!--  <script src="/static/js/echarts.5.5.js"></script>-->
  <script src="/static/js/echarts.4.9.js"></script>
  <style>
    th, td {
      min-width: 100px !important;
      word-break: keep-all;
      white-space: pre
    }

    #c > div + div {
      overflow: hidden !important;
    }

    .E {
      color: red !important;
    }

    .table.small tr > * {
      padding: 3px 6px
    }

    .btn {
      line-height: 1;
    }

    .loading-overlay {
      position: fixed;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      background: rgba(255, 255, 255, 0.8);
      display: flex;
      justify-content: center;
      align-items: center;
      z-index: 9999;
      font-size: 18px;
    }

    .loading-spinner {
      border: 4px solid #f3f3f3;
      border-top: 4px solid #3498db;
      border-radius: 50%;
      width: 40px;
      height: 40px;
      animation: spin 1s linear infinite;
      margin-right: 10px;
    }

    @keyframes spin {
      0% { transform: rotate(0deg); }
      100% { transform: rotate(360deg); }
    }
  </style>
</head>
<body>
<div class="d-flex flex-row" style="border-bottom: 1px solid gray; padding: 4px 6px; height: 40px;align-items: center">
  <div hidden>

  </div>
  <button class="btn btn-primary btn-sm" onclick="main.realtime()" id="realtime">实时</button>
  <button class="btn btn-primary btn-sm mx-1 disactive-btn" onclick="main.history()" id="history" disabled>历史</button>
  日期:<input id="day" type="date" onchange="main.onDay()">
  任务:<select id="task" class="sele-comp" style="min-width: 150px;"></select>
  <label for="packages">载荷:</label><select id="packages" style="min-width: 150px;"></select>
  <button id="queryBtn" class="btn btn-primary btn-sm ms-2" onclick="manualQuery()">
    <i class="fa fa-search"></i>查询
  </button>

  <label for="frm_range" class="ps-3" id="frames_sidx">起始位置:</label>
  <input type="range" min="0" value="0" max="0" id="frm_range" class="flex-grow-1" onchange="main.onFrm()"><span
  style="width: 200px"> </span>
  <a class="btn btn-primary btn-sm" onclick="downloadWithLoading(this)"><i
    class="fa fa-cloud-download"></i>下载</a>

</div>

<!-- Loading overlay -->
<div id="loadingOverlay" class="loading-overlay" style="display: none;">
  <div class="loading-spinner"></div>
  <span>正在查询数据...</span>
</div>

<!-- Download loading overlay -->
<div id="downloadLoadingOverlay" class="loading-overlay" style="display: none;">
  <div class="loading-spinner"></div>
  <span>正在导出数据，请稍候...</span>
</div>

<div style="height: calc(100vh - 42px); width: 100%" class="d-flex flex-row">
  <div style="height: 100%; width: 350px; " class="position-relative">
    <select id="params" size="20" multiple
            style="outline: none;border: 1px solid lightgrey; top: 0;left:0; height: 100%;width: 100%;"
            onchange="main.onParam()"></select>
  </div>

  <div class="flex-grow-1 position-relative">
    <div style="position: absolute;top: 0;bottom: 380px;left:0;right: 0;overflow: auto;font-size: 14px"
         class="bg-light">
      <table id="result" class="table table-bordered table-hover small m-0">
        <thead></thead>
        <tbody></tbody>
      </table>
    </div>
    <div id="c"
         style="overflow: hidden;height: 380px;position: absolute;bottom: 0;right: 0;left: 0; width: 100%;border-top: 1px solid lightgrey"
         class="bg-light"></div>
  </div>
</div>

<script src="/static/js/jquery-3.6.0.min.js"></script>
<script src="/static/js/xtool.js"></script>
<script src="./../qv.js"></script>
<script src="index.js"></script>
<script>
  const local_offset = new Date().getTimezoneOffset() * 60e3

  let db = new Inflex()
  var _tfmt = (t) => t.toISOString().slice(0, 19).replace('T', ' ');

  // Loading状态管理
  let $loadingOverlay = null;
  let $downloadLoadingOverlay = null;
  let $queryBtn = null;

  // 显示loading
  function showLoading() {
    if (!$loadingOverlay) $loadingOverlay = $('#loadingOverlay');
    if (!$queryBtn) $queryBtn = $('#queryBtn');
    $loadingOverlay.show();
    $queryBtn.prop('disabled', true);
  }

  // 隐藏loading
  function hideLoading() {
    if (!$loadingOverlay) $loadingOverlay = $('#loadingOverlay');
    if (!$queryBtn) $queryBtn = $('#queryBtn');
    $loadingOverlay.hide();
    $queryBtn.prop('disabled', false);
  }

  // 显示下载loading
  function showDownloadLoading() {
    if (!$downloadLoadingOverlay) $downloadLoadingOverlay = $('#downloadLoadingOverlay');
    $downloadLoadingOverlay.show();
  }

  // 隐藏下载loading
  function hideDownloadLoading() {
    if (!$downloadLoadingOverlay) $downloadLoadingOverlay = $('#downloadLoadingOverlay');
    $downloadLoadingOverlay.hide();
  }

  // 数据量预检查函数
  async function checkDataVolume(pid, task, station) {
    try {
      let countData = await db.execute(
        `select count(time) as count
         from "${pid}"
         where task = '${task}'
           and station = '${station}'`
      );
      
      if (countData && countData.results && countData.results[0] && countData.results[0].series) {
        const count = countData.results[0].series[0].values[0][1];
        return count || 0;
      }
      return 0;
    } catch (error) {
      console.error('数据量检查失败:', error);
      return 0;
    }
  }

  // 手动查询函数
  async function manualQuery() {
    if (!$('#task').val()) {
      alert('请先选择任务！');
      return;
    }

    showLoading();
    
    try {
      if (main && typeof main.onTask === 'function') {
        await main.onTask();
      }
    } catch (error) {
      console.error('查询失败:', error);
      alert(`查询失败: ${error.message}`);
    } finally {
      hideLoading();
    }
  }

  // 下载包装函数
  async function downloadWithLoading(btn) {
    showDownloadLoading();
    try {
      if (main && typeof main.onDownload === 'function') {
        await main.onDownload(btn);
      }
    } catch (error) {
      console.error('下载失败:', error);
      alert('下载失败，请重试');
    } finally {
      hideDownloadLoading();
    }
  }

  function save() {
    a = document.createElement('a');
    a.href = chart.getDataURL({type: 'png', pixelRatio: 1});
    a.download = current_packages.name + '.png';
    a.click();
  }

  var main

  class XPage extends Realtime {
    constructor(pkgId, task) {
      super(pkgId, getStation());
      this.chart = echarts.init($$('#c'), null, {devicePixelRatio: 2, renderer: 'canvas'})
      this.pageSize = 16
      this.CLIMT = 9
      if (task) {
        this._task = task
        this.$day.value = task.date()
      }

      this.$prm = $('#params');
      this.$frmNumSpan = $('#frm_range+*');
      this.$TH = $('#result>thead');
      this.$TB = $('#result>tbody');
      this.$frmr.step = this.pageSize;
      this.$TB[0].onmousewheel = e => {
        if (e.shiftKey) return
        this.turnPage(e.wheelDelta < 0)
      };
      this.chart.getDom().onclick = this.onClickChart.bind(this)
      window.onresize = this.chart.resize
      this.$task.onchange = this.onTask.bind(this)
      this.$day.onchange = this.onDay.bind(this)
    }

    async onTask() {
      let t = this.$task.value
      if (!t) return
      this._task = new Task(t)
      await this.queryData()
      this.showtable()
      this.renderChart()
    }

    async onDownload(self) {
      let keys = this.$prm.val()
      if (keys.length == 0) keys = this.packages.keys
      // this.packages.n+this._task.label.replace('/','-')
      let n = this.$task.value.split('/')
      let [a, b, c] = n.slice(n.length - 3)
      let savename = `工程遥测_${getStation()}_${this.packages.n}_${a}_${b}`;
      
      // 使用选中的参数进行下载
      try {
        await this.packages.to_csv(keys, self, savename)
      } catch (error) {
        console.error('下载失败:', error);
        throw error; // 重新抛出错误，让上层的downloadWithLoading函数处理
      }
    }

    onClickChart() {
      this.$frmr.value = Math.floor(this.chart.curDataIndex / this.pageSize) * this.pageSize;
      this.showtable();
    }

    turnPage(nextFlag) {
      let val = Number(this.$frmr.value)
      this.$frmr.value = (val + (nextFlag ? +this.pageSize : -this.pageSize));
      this.showtable();
    }

    option() {
      return this.ption = {
        useUTC: true,
        color: ['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de', '#3ba272', '#fc8452', '#9a60b4', '#ea7ccc'],
        xAxis: {type: 'time'},
        yAxis: {type: 'value', scale: true},
        legend: {right: 50, top: 15},
        toolbox: {
          top: 15,
          right: 10,
          orient: 'vertical',
          feature: {saveAsImage: {type: 'png', pixelRatio: 5}, dataZoom: {yAxisIndex: false}}
        },
        grid: {top: 40, left: 80, right: 50, bottom: 70},
        tooltip: {
          trigger: 'axis', axisPointer: {type: 'line', axis: 'x'},
          position: (pos, params, el, elRect, size) => {
            this.chart.curDataIndex = params[0].dataIndex;
          },
          extraCssText: 'background-color: #fff9;color: #444;border: 1px solid #aaa'
        }, 
        animation: false,
        // 性能优化配置
        progressive: 5000,        // 渐进式渲染阈值
        progressiveThreshold: 10000,  // 渐进式渲染的数据量阈值
        hoverLayerThreshold: 3000,    // hover层阈值
        dataZoom: [
          {type: 'slider', height: 20, xAxisIndex: 0, show: true, filterMode: 'weakFilter'},
          {type: 'inside', xAxisIndex: 0, filterMode: 'weakFilter'}]
      }
    }

    async onPackage() {
      let params = await this.loadParams($pkg.val())
      this.packages = new PKGS(params)
      this.chart.clear()
      // 移除自动查询，提示用户手动查询
      this.$TB.html(`<tr><td style="color: #999" colspan="10">请点击查询按钮获取数据</td></tr>`);
    }

    onFrm() {
      this.showtable()
    }

    onParam() {
      // 参数选择变化时不自动查询，只清空显示并提示用户手动查询
      this.$TB.html(`<tr><td style="color: #999" colspan="10">请点击查询按钮获取数据</td></tr>`)
      this.chart.clear()
    }

    addChartListener() {
      this.chart.on('click', function (e) {
        this.$frmr.value = (e.dataIndex);
        this.showtable()
      })
    }

    async load_task(day, reset) {
      day = day || this.$day.value;
      if (!day) return
      this.tasks = await $api(`omsp.tasks?ins=YC/${this.station}&day=${day}`)
      let $t = this.$task;
      $t.innerHTML = this.tasks.map(v => new Task(v).option()).join('')
      if (reset) {
        $t.selectedIndex = 0;
      } else {
        if (this._task) $t.value = this._task.task
      }
      return $t.value
    }

    async onDay() {
      this._task = ''
      let day = this.$day.value
      if (!day) return
      await this.load_task(day, true)
      // 移除自动查询，提示用户手动查询
      this.$TB.html(`<tr><td style="color: #999" colspan="10">请点击查询按钮获取数据</td></tr>`);
      this.chart.clear();
    }

    realtime() {
      window.location.href = 'rt.html'
    }

    async history() {
      super.history()
      let params = await this.loadParams($pkg.val())
      this.packages = new PKGS(params)
      let day = this.$day.value
      if (!day) return
      await this.load_task(day, true)
      // 移除自动查询，提示用户手动查询
      this.$TB.html(`<tr><td style="color: #999" colspan="10">请点击查询按钮获取数据</td></tr>`);
      this.chart.clear();
    }

    async queryData() {
      let pid = $pkg.val()
      let taskSplite = this.$task.value.split('/')
      let task = taskSplite[taskSplite.length - 2]
      let station = getStation()

      // 获取选中的参数，确保包含time字段
      let selected = this.$prm.val();
      let columns = ['time'];
      if (selected && selected.length > 0) {
        columns = ['time', ...selected];
      } else {
        columns = ['*'];
      }

      // 数据量预检查
      const dataCount = await checkDataVolume(pid, task, station);
      let sampleClause = '';
      let useDataSampling = false;
      
      // 设置采样阈值
      const SAMPLE_THRESHOLD = 50000;  // 超过5万条数据启用采样
      const DISPLAY_THRESHOLD = 10000; // 超过1万条数据提示用户
      
      if (dataCount > DISPLAY_THRESHOLD) {
        console.log(`数据量: ${dataCount} 条`);
        
        if (dataCount > SAMPLE_THRESHOLD) {
          // 计算采样率
          const sampleRate = Math.ceil(dataCount / 10000); // 目标采样到1万条左右
          const sampleInterval = `${sampleRate}s`;
          sampleClause = ` group by time(${sampleInterval}) fill(previous)`;
          useDataSampling = true;
          
          // 提示用户
          alert(`检测到大数据量（${dataCount}条），已自动启用智能采样（每${sampleRate}条取1条），确保页面流畅运行。\n如需完整数据，请缩小查询时间范围。`);
        } else {
          // 数据量较大但未达到采样阈值，提示用户
          console.log(`数据量较大（${dataCount}条），可能需要较长加载时间`);
        }
      }

      // 为字段名添加双引号分隔符，避免数字开头的字段名解析错误
      let quotedColumns = columns.map(col => col === '*' ? '*' : `"${col}"`);
      
      // 构建查询SQL，根据数据量决定是否采样
      let sql;
      if (useDataSampling && columns[0] !== '*') {
        // 使用采样查询（针对具体字段）
        const fieldSelections = quotedColumns.map(col => 
          col === '"time"' ? 'time' : `last(${col}) as ${col}`
        ).join(', ');
        
        sql = `select ${fieldSelections}
               from "${pid}"
               where task = '${task}'
                 and station = '${station}'
               ${sampleClause}
               order by time`;
      } else {
        // 常规查询或全字段查询
        sql = `select ${quotedColumns.join(', ')}
               from "${pid}"
               where task = '${task}'
                 and station = '${station}'
               order by time`;
      }
      
      console.log('执行查询:', sql);
      let data = await db.execute(sql);
      
      let pkgs = this.packages.dataset(data)
      
      // 如果启用了采样，在数据对象中标记
      if (useDataSampling && this.packages) {
        this.packages.isSampled = true;
        this.packages.originalCount = dataCount;
      }
      
      this.$frmr.max = pkgs ? pkgs.length : 0;
      return pkgs
    }

    renderChart() {
      this.chart.clear()
      let selected = this.$prm.val()
      let len = selected.length
      if (len > 0 && len < this.CLIMT) {
        this.chart.showLoading()
        try {
          let d = selected.map(k => {
            let seriesData = this.packages.data(k);
            
            // 检查数据量，如果过大则进行前端采样
            if (seriesData.data && seriesData.data.length > 5000) {
              console.log(`系列 ${seriesData.name} 数据量过大(${seriesData.data.length})，进行前端采样优化`);
              seriesData = this.sampleSeriesData(seriesData, 5000);
            }
            
            // 添加大数据优化配置
            seriesData.large = true;           // 启用大数据模式
            seriesData.largeThreshold = 2000;  // 大数据模式阈值
            seriesData.sampling = 'lttb';      // 使用LTTB采样算法
            seriesData.progressiveChunkMode = 'sequential';  // 渐进式渲染模式
            
            return seriesData;
          });
          
          let names = d.map(v => v.name)
          
          // 设置图表配置
          this.chart.setOption(this.option())
          
          // 添加采样提示
          let titleText = this.packages.n;
          if (this.packages.isSampled) {
            titleText += ` (已采样: ${this.packages.values.length}/${this.packages.originalCount}条)`;
          }
          
          this.chart.setOption({
            series: d, 
            title: {text: titleText}, 
            legend: {data: names}
          })
        } catch (e) {
          console.error(e)
        } finally {
          this.chart.hideLoading()
        }
      }
    }

    // 数据采样函数 - 使用最大三角形三桶算法(LTTB)
    sampleSeriesData(seriesData, targetCount) {
      const data = seriesData.data;
      if (!data || data.length <= targetCount) {
        return seriesData;
      }
      
      const sampled = [];
      const bucketSize = Math.floor(data.length / targetCount);
      
      // 保留第一个点
      sampled.push(data[0]);
      
      for (let i = 0; i < targetCount - 2; i++) {
        const bucketStart = (i + 1) * bucketSize;
        const bucketEnd = Math.min(bucketStart + bucketSize, data.length - 1);
        
        // 找到桶内最有代表性的点
        let maxArea = 0;
        let maxAreaIndex = bucketStart;
        
        for (let j = bucketStart; j < bucketEnd; j++) {
          // 计算三角形面积
          const area = Math.abs(
            (data[j][0] - sampled[sampled.length - 1][0]) * (data[bucketEnd][1] - sampled[sampled.length - 1][1]) -
            (data[j][1] - sampled[sampled.length - 1][1]) * (data[bucketEnd][0] - sampled[sampled.length - 1][0])
          );
          
          if (area > maxArea) {
            maxArea = area;
            maxAreaIndex = j;
          }
        }
        
        sampled.push(data[maxAreaIndex]);
      }
      
      // 保留最后一个点
      sampled.push(data[data.length - 1]);
      
      return {
        ...seriesData,
        data: sampled,
        _originalLength: data.length  // 记录原始数据长度
      };
    }

    async loadParams(pid) {
      let q = await fetch(pid + '.json').then(v => v.json())
      this.$prm.html(q.TmParas.map(v => {
        r = document.createElement('option')
        r.data = v
        r.value = v.id;
        r.innerHTML = v.n;
        return r;
      }))
      return q
    }


    showtable(keys, pkgs) {
      let selected = this.$prm.val()
      keys = this.keys || (selected.length > 0 ? selected : this.packages.keys);
      pkgs = pkgs || this.packages.values;
      let s = Number(this.$frmr.value);
      this.$TH.html(this.packages.thead(keys));
      if (pkgs && pkgs.length > 0) {
        this.$TB.html('')
        this.$frmNumSpan.html(`${s}/${pkgs.length}`);
        pkgs.slice(s, s + this.pageSize).forEach(pkg => this.$TB.append(pkg.tr(keys)));
      } else {
        this.$frmNumSpan.html(`0/0`);
        this.$frmr.max = 0;
        this.$TB.html(`<tr><td style="color: #F00" colspan="${keys.length + 1}"> 无数据</td></tr>`)
      }
    }

  }

  (async () => {
    let params = new URLSearchParams(window.location.search)
    console.log(params.get('t'))
    await initPckOptions()
    let pkgId = params.get('vid')
    $pkg.val(pkgId)
    let taskParam = params.get('t')
    let task = ''
    if (taskParam) task = new Task(taskParam)

    main = new XPage(pkgId, task)
    await main.history()
    
    // 初始化时提示用户手动查询
    if (!taskParam) {
      main.$TB.html(`<tr><td style="color: #999" colspan="10">请选择日期和任务后点击查询按钮获取数据</td></tr>`);
    }


  })()

</script>
</body>
</html>